package pt.ua.miect.sd.simuladorpesca.entities;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import pt.ua.miect.sd.simuladorpesca.SimulationConfig;

public class MapPosition {

    private int xCoordinate;
    private int yCoordinate;

    public MapPosition(int xCoord, int yCoord) {
        this.xCoordinate = xCoord;
        this.yCoordinate = yCoord;
    }

    /**
     * @return the xCoordinate
     */
    public int getXCoordinate() {
        return xCoordinate;
    }

    /**
     * @return the yCoordinate
     */
    public int getYCoordinate() {
        return yCoordinate;
    }

    @Override
    public String toString() {

        return String.format("(%s,%s)", xCoordinate, yCoordinate);
    }

    /**
     * Evaluates if the given coordinates are equal to those of the instance
     * @param xpos
     * @param ypos
     * @return
     */
    public boolean equals(int xpos, int ypos) {
        return (this.xCoordinate == xpos && this.yCoordinate == ypos);
    }

    /**
     * Evalutes if given object is equal to the current instance
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof MapPosition) {
            MapPosition pos = (MapPosition) obj;
            return pos.equals(this.xCoordinate, this.yCoordinate);
        } else {
            return super.equals(obj);
        }
    }

    @Override
    public int hashCode() {
        return this.toString().hashCode();
    }

    /**
     * Return a valid random position on the map
     * @return
     */
    public static MapPosition genRandomPosition() {

        int xpos = (int) (Math.random() * SimulationConfig.MAX_X_POSITION);
        int ypos = (int) (Math.random() * SimulationConfig.MAX_Y_POSITION);
        return new MapPosition(xpos, ypos);

    }

    /**
     * Returns true if the given position is adjacent to the current one.
     * @param targetPosition
     * @return
     */
    public boolean isAdjacent(MapPosition targetPosition) {
        int xDistance = Math.abs(xCoordinate - targetPosition.getXCoordinate());
        int yDistance = Math.abs(yCoordinate - targetPosition.getYCoordinate());
        if ((xDistance == 1 && yDistance == 0) || (xDistance == 0 && yDistance == 1)) {
            return true;
        } else {
            return false;
        }
    }

    /*
     * Generate a Valid map position based on the current position
     */
    public static List<MapPosition> getValidMovements(MapPosition startPosition) {
        LinkedList<MapPosition> moves = new LinkedList<MapPosition>();
        int positions[][] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; //all valid movements
        int randomSeed = 234566;
        boolean valid = false;
        MapPosition newPos = null;
        for (int pos = 0; pos < 4; pos++) {
            int newX = startPosition.getXCoordinate() + positions[pos][0];
            int newY = startPosition.getYCoordinate() + positions[pos][1];
            if (newX >= SimulationConfig.MAX_X_POSITION) {
                valid = false;
            } else if (newX < 0) {
                valid = false;
            } else if (newY >= SimulationConfig.MAX_Y_POSITION) {
                valid = false;
            } else if (newY < 0) {
                valid = false;
            } else if (newX == SimulationConfig.PORT_HOME_X && newY == SimulationConfig.PORT_HOME_Y) {
                valid = false;
            } else {
                //then it is a valid position
                valid = true;
                newPos = new MapPosition(newX, newY);
            }
            if (moves.isEmpty()) {
                moves.add(newPos);
            } else if (moves.size() < 2) {
                int index = (int) Math.round(Math.random());
                moves.add(index,newPos);
            } else {
                int index = (int) ((Math.random() * randomSeed) % moves.size());
                moves.add(index,newPos);
            }
        }
        return moves;
    }

    /*
     * Gets the a random shoal area
     */
    public static MapPosition getNearestShoalArea(MapPosition currentPos) {
        int randomSeed = 565482;
        int y;
        int x;
        boolean valid = false;
        MapPosition pos = null;
        while (!valid) {
            x = currentPos.getXCoordinate();
            if (currentPos.getYCoordinate() > SimulationConfig.MAX_SHOAL_Y) {
                y = (int) ((Math.random() * randomSeed) % SimulationConfig.MAX_SHOAL_Y);
            } else {
                y = currentPos.getYCoordinate();

            }
            if (x == SimulationConfig.PORT_HOME_X && y == SimulationConfig.PORT_HOME_Y) {
                valid = false;
            } else {
                pos = new MapPosition(x, y);
                valid = true;
            }
        }

        return pos;

    }

    /*
     * Gets a random Shoal position
     */
    public static MapPosition getRandomShoalArea() {
        int randomSeed = 565482;
        boolean valid = false;
        MapPosition pos = null;
        while (!valid) {
            int x = (int) ((Math.random() * randomSeed) % SimulationConfig.MAX_X_POSITION);
            int y = (int) ((Math.random() * randomSeed) % SimulationConfig.MAX_SHOAL_Y);
            if (x == SimulationConfig.PORT_HOME_X && y == SimulationConfig.PORT_HOME_Y) {
                valid = false;
            } else {
                pos = new MapPosition(x, y);
                valid = true;
            }
        }

        return pos;

    }

    /*
     * Gets the best paths to be used
     */
    public static List<MapPosition> getRoutesTo(MapPosition from, MapPosition to, boolean bestRoutesOnly) {
        LinkedList<MapPosition> routes = new LinkedList<MapPosition>();
        int moves[][] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
        int distance = from.getDistance(to);

        for (int i = 0; i < 4; i++) {
            int newX = from.getXCoordinate() + moves[i][0];
            int newY = from.getYCoordinate() + moves[i][1];

            if (newX == to.getXCoordinate() && newY == to.getYCoordinate()) {
                //is target position
                routes.clear();
                routes.add(to);
                return routes;
            }
            boolean valid = false;
            if (newX >= SimulationConfig.MAX_X_POSITION) {
                valid = false;
            } else if (newX < 0) {
                valid = false;
            } else if (newY >= SimulationConfig.MAX_Y_POSITION) {
                valid = false;
            } else if (newY < 0) {
                valid = false;
            } else if (newX == SimulationConfig.PORT_HOME_X && newY == SimulationConfig.PORT_HOME_Y) {
                valid = false;
            } else {
                valid = true;

            }
            if (!valid) {
                continue;
            }
            int newdist = Math.abs(newX - to.getXCoordinate()) + Math.abs(newY - to.getYCoordinate());
            if (distance < newdist) { //if it takes longer beter  to wait
                continue;
            }
            if (bestRoutesOnly && distance > newdist) {
                routes.add(new MapPosition(newX, newY));
            } else if (distance > newdist) {
                routes.addFirst(new MapPosition(newX, newY));
            } else {
                routes.addLast(new MapPosition(newX, newY));
            }
        }
        return routes;
    }

    /**
     * Calculates the distance to the given point
     * @param to
     * @return
     */
    public int getDistance(MapPosition to) {
        return Math.abs(this.getXCoordinate() - to.getXCoordinate()) + Math.abs(this.getYCoordinate() - to.getYCoordinate());

    }
}
